﻿#define TcpClientVersion
//#define Debug

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace DD.DDTelnetLib
{
    public class DDTelnetClient
    {
        public struct ANSIColorCode
        {
            public bool Change;
            public bool Bright;
            public int TextColor;
            public int BackgroundColor;
        }

        public enum ANSICodeState
        {
            NormalText,
            ESC_Found,
            OpenBracket_Found,
            Only_ESC_Found,
        }

        public enum TelnetProtocolState
        {
            NormalText,
            IAC_Found,
            IAC_WILL_Found,
            IAC_WONT_Found,
            IAC_DO_Found,
            IAC_DONT_Found,
            IAC_SB_Found,
            IAC_SB_IAC_Found,
        }

        public char SE = Convert.ToChar(240);
        public char NOP = Convert.ToChar(241);
        public char DM = Convert.ToChar(242);
        public char BRK = Convert.ToChar(243);
        public char IP = Convert.ToChar(244);
        public char AO = Convert.ToChar(245);
        public char AYT = Convert.ToChar(246);
        public char EC = Convert.ToChar(247);
        public char EL = Convert.ToChar(248);
        public char GA = Convert.ToChar(249);
        public char SB = Convert.ToChar(250);

        public char WILL = Convert.ToChar(251);
        public char WONT = Convert.ToChar(252);
        public char DO = Convert.ToChar(253);
        public char DONT = Convert.ToChar(254);
        public char IAC = Convert.ToChar(255);

        private TcpClient Client = null;
        private NetworkStream ClientStream = null;
        private Byte[] ClientBuffer = new Byte[32767];
        private Byte[] ClientBuffer2 = new Byte[32767];
        private Thread Reader = null;
        private bool StopReader = false;
        private bool ReaderEnd = true;
        public int SleepTime = 1;

        Encoding Big5Encoding = Encoding.GetEncoding("big5");

        public string TPString = "";
        public TelnetProtocolState TPState = TelnetProtocolState.NormalText;
        public string ACString = "";
        public ANSICodeState ACState = ANSICodeState.NormalText;

        public ANSIColorCode ANSIStat = new ANSIColorCode();
        private string ReceivedMessage = "";

        public DDTelnetClient()
        {
            SetANSIStat();
        }

        public DDTelnetClient(string Hostname, int Port)
            : this()
        {
            Connect(Hostname, Port);
        }

        public bool Connect(string Hostname, int Port)
        {
            try
            {
                if (Client == null)
                {
                    Client = new TcpClient(Hostname, Port);
                    ClientStream = Client.GetStream();
                }

                if (ReaderEnd == true)
                {
                    Reader = null;
                    ReaderEnd = false;
                }

                if (Reader == null)
                {
                    Reader = new Thread(ThreadReader);
                    Reader.IsBackground = true;
                    StopReader = false;
                    ReaderEnd = false;
                    Reader.Start();
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
                return (false);
            }

            return (true);
        }

        public void Disconnect()
        {
            StopReader = true;

            if (Client != null)
            {
                Client.Close();
                Client = null;
            }

            if (ClientStream != null)
            {
                ClientStream.Close();
            }

            SetANSIStat();
            ReceivedMessage = "";
        }

        public bool IsConnecting
        {
            get
            {
                if (Client == null || !Client.Connected)
                {
                    return (false);
                }
                return (true);
            }
        }

        private void ThreadReader()
        {
            UTF8Encoding UTF8Encoder = new UTF8Encoding();
            int ReadLength;
            int i, j;

            while (!StopReader)
            {
                if (ClientStream.CanRead && ClientStream.DataAvailable)
                {
                    if ((ReadLength = ClientStream.Read(ClientBuffer, 0, ClientBuffer.Length)) > 0)
                    {
                        for (i = 0, j = 0; i < ReadLength; i++)
                        {
                            if (!TelnetProtocolCheck(Convert.ToChar(ClientBuffer[i])))
                            {
                                ClientBuffer2[j] = ClientBuffer[i];
                                j++;
                            }
                        }
                        byte[] UTF8Bytes = Encoding.Convert(Big5Encoding, Encoding.UTF8, ClientBuffer2, 0, j);
                        ReceivedMessage += UTF8Encoder.GetString(UTF8Bytes);
                    }
                }
                Thread.Sleep(1);
            }
            ReaderEnd = true;
        }

        public bool NextCharAvailable
        {
            get
            {
                return (ReceivedMessage.Length > 0);
            }
        }

        public bool GetNextChar(out char NextChar)
        {
            bool Finished = false;

            NextChar = '\0';
            while (ReceivedMessage.Length > 0 && !Finished)
            {
                NextChar = ReceivedMessage.First();
                ReceivedMessage = ReceivedMessage.Remove(0, 1);
                if (!ANSICodeCheck(NextChar))
                {
                    switch (NextChar)
                    {
                        case '\n':
                            Finished = true;
                            break;
                        case '\r':
                            break;
                        default:
                            Finished = true;
                            break;
                    }
                }
            }
            return (Finished);
        }

        public bool SendString(string Str)
        {
            try
            {
                if (!ClientStream.CanWrite)
                {
                    return (false);
                }

                byte[] UTF8Bytes = Encoding.UTF8.GetBytes(Str);
                byte[] WriteBuffer = Encoding.Convert(Encoding.UTF8, Big5Encoding, UTF8Bytes, 0, UTF8Bytes.Length);
                ClientStream.Write(WriteBuffer, 0, WriteBuffer.Length);
            }
            catch (IOException e)
            {
                Console.WriteLine("IOException: {0}", e);
                return (false);
            }
            return (true);
        }

        public bool SendString(byte[] WriteBuffer)
        {
            if (!ClientStream.CanWrite)
            {
                return (false);
            }

            ClientStream.Write(WriteBuffer, 0, WriteBuffer.Length);

            return (true);
        }

        private bool TelnetProtocolCheck(char C)
        {
            bool IsTP = false;

            if (TPState == TelnetProtocolState.NormalText)
            {
                if (C == IAC)
                {
                    TPState = TelnetProtocolState.IAC_Found;
                    TPString = "";
                    IsTP = true;
                }
            }
            else if (TPState == TelnetProtocolState.IAC_Found)
            {
                if (C == IAC)
                {
                    TPState = TelnetProtocolState.NormalText;
                }
                else if (C == WILL)
                {
                    TPState = TelnetProtocolState.IAC_WILL_Found;
                    IsTP = true;
                }
                else if (C == WONT)
                {
                    TPState = TelnetProtocolState.IAC_WONT_Found;
                    IsTP = true;
                }
                else if (C == DO)
                {
                    TPState = TelnetProtocolState.IAC_DO_Found;
                    IsTP = true;
                }
                else if (C == DONT)
                {
                    TPState = TelnetProtocolState.IAC_DONT_Found;
                    IsTP = true;
                }
                else if (C == SB)
                {
                    TPState = TelnetProtocolState.IAC_SB_Found;
                    IsTP = true;
                }
                else
                {
                    TPState = TelnetProtocolState.NormalText;
                    IsTP = true;
                }
            }
            else if (TPState == TelnetProtocolState.IAC_WILL_Found)
            {
                TPState = TelnetProtocolState.NormalText;
                TPString += C;
                IsTP = true;
            }
            else if (TPState == TelnetProtocolState.IAC_WONT_Found)
            {
                TPState = TelnetProtocolState.NormalText;
                TPString += C;
                IsTP = true;
            }
            else if (TPState == TelnetProtocolState.IAC_DO_Found)
            {
                TPState = TelnetProtocolState.NormalText;
                TPString += C;
                IsTP = true;
            }
            else if (TPState == TelnetProtocolState.IAC_DONT_Found)
            {
                TPState = TelnetProtocolState.NormalText;
                TPString += C;
                IsTP = true;
            }
            else if (TPState == TelnetProtocolState.IAC_SB_Found)
            {
                if (C == IAC)
                {
                    TPState = TelnetProtocolState.IAC_SB_IAC_Found;
                    IsTP = true;
                }
                else
                {
                    TPString += C;
                    IsTP = true;
                }
            }
            else if (TPState == TelnetProtocolState.IAC_SB_IAC_Found)
            {
                if (C == SE)
                {
                    IsTP = true;
                }
                else
                {
                    TPState = TelnetProtocolState.IAC_SB_Found;
                    IsTP = true;
                }
            }
            else
            {
            }

            return (IsTP);
        }

        private bool ANSICodeCheck(char C)
        {
            bool IsAC = false;

            if (ACState == ANSICodeState.NormalText)
            {
                if (C == '\x1b')
                {
                    ACState = ANSICodeState.ESC_Found;
                    ACString = "";
                    IsAC = true;
                }
            }
            else if (ACState == ANSICodeState.ESC_Found)
            {
                if(C == '[')
                {
                    ACState = ANSICodeState.OpenBracket_Found;
                    IsAC = true;
                }
                else if(C == '\x1b')
                {
                    ACState = ANSICodeState.ESC_Found;
                    ACString = "";
                }
                else
                {
                    ACState = ANSICodeState.Only_ESC_Found;
                }
            }
            else if(ACState == ANSICodeState.OpenBracket_Found)
            {
                if(C == 'm')
                {
                    ACState = ANSICodeState.NormalText;
                    IsAC = true;
                    ChangeANSIColor();
                }
                else if (C == '\x1b')
                {
                    ACState = ANSICodeState.ESC_Found;
                    ACString = "";
                }
                else
                {
                    ACString += C;
                    IsAC = true;
                }
            }
            else
            {
            }

            return (IsAC);
        }

        private void ChangeANSIColor()
        {
            bool Bright = ANSIStat.Bright;
            int TextColor = ANSIStat.TextColor;
            int BackgroundColor = ANSIStat.BackgroundColor;
            int TempCode;

            ACString = ACString.Trim();
            if (ACString.Length != 0)
            {
                for (int i = 0, j = 0; j != -1; i = j + 1)
                {
                    j = ACString.IndexOf(';', i);
                    if (i != j)
                    {
                        if (j == -1)
                        {
                            TempCode = Convert.ToInt32(ACString.Substring(i));
                        }
                        else
                        {
                            TempCode = Convert.ToInt32(ACString.Substring(i, j - i));
                        }
                    }
                    else
                    {
                        TempCode = -1;
                    }
                    if (TempCode == 0)
                    {
                        Bright = false;
                        TextColor = 7;
                        BackgroundColor = 0;
                    }
                    else if (TempCode == 1)
                    {
                        Bright = true;
                    }
                    else if (TempCode >= 30 && TempCode <= 37)
                    {
                        TextColor = TempCode - 30;
                    }
                    else if (TempCode >= 40 && TempCode <= 47)
                    {
                        BackgroundColor = TempCode - 40;
                    }
                    else
                    {
                    }
                }
                SetANSIStat(Bright, TextColor, BackgroundColor);
            }
            else
            {
                SetANSIStat();
            }
        }

        private void SetANSIStat()
        {
            SetANSIStat(false, 7, 0);
        }

        private void SetANSIStat(bool Bright, int TextColor, int BackgroundColor)
        {
            ANSIStat.Change = true;
            ANSIStat.Bright = Bright;
            ANSIStat.TextColor = TextColor;
            ANSIStat.BackgroundColor = BackgroundColor;
        }

        public void InsertMessageLine(string Msg)
        {
            int Target = ReceivedMessage.LastIndexOf('\n');

            if (Target == -1)
            {
                Target = ReceivedMessage.Length;
            }
            else
            {
                Target++;
            }
            ReceivedMessage = ReceivedMessage.Insert(Target, Msg + '\n');
        }
    }
}
